#include "StdAfx.h"
#include "Material.h"
#include "MeshMaterialShader.h"
#include "VertexFactory.h"

using namespace RenderSystem;

///////////////////////////////////////////////////MeshMaterialShaderType///////////////////////////////////////////////////
void MeshMaterialShaderType::BeginCompileShader(
	const ShaderProfile& rShaderProfile,
	const Material* pMaterial,
	const TCHAR* pszMaterialShaderCode,
	VertexFactoryType* pVertexFactoryType
	)
{
	assert( pMaterial != NULL && pszMaterialShaderCode != NULL && pVertexFactoryType != NULL );
	// Construct the shader environment.
	ShaderCompilerEnvironment Environment;
	Environment.IncludeFiles.insert(TEXT("Material.usf"),pszMaterialShaderCode);

	switch( pMaterial->GetBlendMode() )
	{
	case Material::BLEND_Opaque: Environment.Definitions.insert(TEXT("MATERIALBLENDING_SOLID"),TEXT("1")); break;
	case Material::BLEND_Masked: Environment.Definitions.insert(TEXT("MATERIALBLENDING_MASKED"),TEXT("1")); break;
	case Material::BLEND_Translucent: Environment.Definitions.insert(TEXT("MATERIALBLENDING_TRANSLUCENT"),TEXT("1")); break;
	case Material::BLEND_Additive: Environment.Definitions.insert(TEXT("MATERIALBLENDING_ADDITIVE"),TEXT("1")); break;
	case Material::BLEND_Modulate: Environment.Definitions.insert(TEXT("MATERIALBLENDING_MODULATE"),TEXT("1")); break;
	default: assert( false );
	}

	Environment.Definitions.insert(TEXT("MATERIAL_TWOSIDED"),pMaterial->IsTwoSided() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_TWOSIDED_SEPARATE_PASS"),pMaterial->RenderTwoSidedSeparatePass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_LIT_TRANSLUCENCY_PREPASS"),pMaterial->RenderLitTranslucencyPrepass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_LIT_TRANSLUCENCY_DEPTH_POSTPASS"),pMaterial->RenderLitTranslucencyDepthPostpass() ? TEXT("1") : TEXT("0"));
	//Environment.Definitions.insert(TEXT("MATERIAL_CAST_LIT_TRANSLUCENCY_SHADOW_AS_MASKED"),pMaterial->CastLitTranslucencyShadowAsMasked() ? TEXT("1") : TEXT("0"));

	switch(pMaterial->GetLightingModel())
	{
	case Material::MLM_SHPRT: // For backward compatibility, treat the deprecated SHPRT lighting model as Phong.
	case Material::MLM_Phong: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_PHONG"),TEXT("1")); break;
	case Material::MLM_NonDirectional: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_NONDIRECTIONAL"),TEXT("1")); break;
	case Material::MLM_Unlit: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_UNLIT"),TEXT("1")); break;
	case Material::MLM_Custom: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_CUSTOM"),TEXT("1")); break;
	case Material::MLM_Anisotropic: Environment.Definitions.insert(TEXT("MATERIAL_LIGHTINGMODEL_ANISOTROPIC"),TEXT("1")); break;
	default: assert( false );
	};

	// Disable specular if the material is flagged as such...
	if (pMaterial->IsSpecularAllowed() == FALSE)
	{
		Environment.Definitions.insert(TEXT("DISABLE_LIGHTMAP_SPECULAR"), TEXT("1"));
		Environment.Definitions.insert(TEXT("DISABLE_DYNAMIC_SPECULAR"), TEXT("1"));
	}

	//if( pMaterial->GetTransformsUsed() & UsedCoord_World 
	//	|| Material->GetTransformsUsed() & UsedCoord_View
	//	|| Material->GetTransformsUsed() & UsedCoord_Local )
	//{
	//	// only use WORLD_COORDS code if a Transform expression was used by the material
	//	Environment.Definitions.Set(TEXT("WORLD_COORDS"),TEXT("1"));
	//}

	//if( pMaterial->GetTransformsUsed() & UsedCoord_WorldPos )
	//{
	//	Environment.Definitions.Set(TEXT("WORLD_POS"),TEXT("1"));
	//}

	Environment.Definitions.insert(TEXT("WORLD_POS"),TEXT("1"));

	// decals always need WORLD_COORD usage in order to pass 2x2 matrix for normal transform
	// using the color interpolators used by WORLD_COORDS
	//if( Material->IsUsedWithDecals() || Material->IsSpecialEngineMaterial() )
	//{
	//	Environment.Definitions.Set(TEXT("WORLD_COORDS"),TEXT("1"));
	//}
	//if( Material->IsUsedWithDecals() )
	//{
	//	Environment.Definitions.Set(TEXT("MATERIAL_DECAL"),TEXT("1"));
	//}

	if( pMaterial->UsesOneLayerDistortion() )
	{
		Environment.Definitions.insert(TEXT("MATERIAL_ONELAYERDISTORTION"),TEXT("1"));
	}

	if( pMaterial->IsUsedWithGammaCorrection() )
	{
		// only use USE_GAMMA_CORRECTION code when enabled
		Environment.Definitions.insert(TEXT("MATERIAL_USE_GAMMA_CORRECTION"),TEXT("1"));
	}

	//if( pMaterial->HasNormalmapConnected() )
	//{
	//	// This is used by the Anisotropic shader to determine whether to perform Graham-Schmidt Orthonormalization
	//	Environment.Definitions.Set(TEXT("MATERIAL_DEFINED_NORMALMAP"),TEXT("1"));
	//}

	//calculate the CRC for this type and store it in the shader cache.
	//this will be used to determine when shader files have changed.
	//DWORD CurrentCRC = GetSourceCRC();
	//UShaderCache::SetShaderTypeCRC(this, CurrentCRC, Platform);

	//warnf(NAME_DevShadersDetailed, TEXT("		%s"), GetName());

	//update material shader stats
	//UpdateMaterialShaderCompilingStats(Material);

	// Enqueue the compilation
	ShaderType::BeginCompileShader(pVertexFactoryType, rShaderProfile, Environment);
}

Shader* MeshMaterialShaderType::FinishCompileShader(
	const Material* pMaterial,
	const ShaderCompileJob& rCurrentJob)
{
	assert(rCurrentJob.bSucceeded);
	// Check for shaders with identical compiled code.
	Shader* pShader = _FindShaderByOutput( rCurrentJob.objOutput );
	if(pShader != NULL)
	{
		// Create the shader.
		pShader = (*m_funConstructCompiledType)(CompiledShaderInitializerType(this, rCurrentJob.objOutput, pMaterial, rCurrentJob.pVFType));
		//rCurrentJob.objOutput.m_ParameterMap.VerifyBindingsAreComplete(GetName(), (EShaderFrequency)CurrentJob.Output.Target.Frequency);
	}
	return pShader;
}

/////////////////////////MeshMaterialShaderMap/////////

MeshMaterialShaderMap::MeshMaterialShaderMap(void)
	:m_pVertexFactoryType(NULL)
{
}

MeshMaterialShaderMap::~MeshMaterialShaderMap(void)
{
}

void MeshMaterialShaderMap::BeginCompile(
			const Material* pMaterial,
			const TCHAR* pszMaterialShaderCode,
			VertexFactoryType* pVertexFactoryType,
			const ShaderProfile& rShaderProfile
			)
{
	assert( pMaterial != NULL && pszMaterialShaderCode != NULL && pVertexFactoryType != NULL );
	
	UINT NumShadersPerVF = 0;
	m_pVertexFactoryType = pVertexFactoryType;

	ShaderType::ShaderTypes& rShaderTypes = ShaderType::GetTypeSets();
	for( ShaderType::ShaderTypesIterator itrShaderType = rShaderTypes.begin();
		itrShaderType != rShaderTypes.end();
		++itrShaderType )
	{
		MeshMaterialShaderType* pMeshMaterialShaderType = (*itrShaderType)->GetMeshMaterialShaderType();
		if( pMeshMaterialShaderType == NULL )
			continue;

		if( pMeshMaterialShaderType->HasShader() )
			continue;

		pMeshMaterialShaderType->BeginCompileShader( rShaderProfile, pMaterial, pszMaterialShaderCode, pVertexFactoryType );
	}
	
	/*
	if (NumShadersPerVF > 0)
	{
		warnf(NAME_DevShadersDetailed, TEXT("		%s - %u shaders"), VertexFactoryType->GetName(), NumShadersPerVF);
	}

	//calculate the CRC for this vertex factory type and store it in the shader cache.
	//this will be used to determine when vertex factory shader files have changed.
	DWORD VertexFactoryCRC = VertexFactoryType->GetSourceCRC();
	UShaderCache::SetVertexFactoryTypeCRC(VertexFactoryType, VertexFactoryCRC, Platform);
	*/
}


/**
 * Creates shaders for all of the compile jobs and caches them in this shader map.
 * @param Material - The material to compile shaders for.
 * @param CompilationResults - The compile results that were enqueued by BeginCompile.
 */
void MeshMaterialShaderMap::FinishCompile(const Material* pMaterial, const ShaderCompileJobs& rCompilationResults)
{
	// Find the matching FMeshMaterialShaderType for each compile job
	for (INT uJobIndex = 0; uJobIndex != rCompilationResults.size(); uJobIndex++)
	{
		const ShaderCompileJob& rCurrentJob = rCompilationResults[uJobIndex];
		if (rCurrentJob.pVFType == m_pVertexFactoryType)
		{
			ShaderType::ShaderTypes types = ShaderType::GetTypeSets();
			for( ShaderType::ShaderTypesIterator itrType = types.begin(); itrType != types.end(); itrType++ )
			{
				MeshMaterialShaderType* pMeshMaterialShaderType = (*itrType)->GetMeshMaterialShaderType();
				if( pMeshMaterialShaderType == NULL )
					continue;
				Shader* pShader = pMeshMaterialShaderType->FinishCompileShader( pMaterial, rCurrentJob );
				AddShader( pMeshMaterialShaderType, pShader );
			}
		}
	}
}
